package reliability;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import reliability.model.GraphModel;
import reliability.types.Settings;
import reliability.types.graph.*;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.File;
import java.io.IOException;

public class XmlParser
{
    public static final String CONFIGURATION_TAG_NAME = "configuration";
    public static final String SETTINGS_TAG_NAME = "settings";
    public static final String DEFAULT_NODE_RELIABILITY_TAG_NAME = "defaultNodeReliability";
    public static final String DEFAULT_EDGE_RELIABILITY_TAG_NAME = "defaultEdgeReliability";
    public static final String NUMBER_OF_TESTS_TAG_NAME = "numOfTests";
    public static final String GRAPH_TAG_NAME = "graph";
    public static final String NODES_TAG_NAME = "nodes";
    public static final String NODE_TAG_NAME = "node";
    public static final String EDGES_TAG_NAME = "edges";
    public static final String EDGE_TAG_NAME = "edge";
    public static final String INDEX_ATTRIBUTE_NAME = "index";
    public static final String RELIABILITY_ATTRIBUTE_NAME = "reliability";
    public static final String X_ATTRIBUTE_NAME = "x";
    public static final String Y_ATTRIBUTE_NAME = "y";
    public static final String NODE1_ATTRIBUTE_NAME = "node1";
    public static final String NODE2_ATTRIBUTE_NAME = "node2";


    public GraphModel parse(File file)
    {
        GraphModel graphModel = null;
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        try
        {
            DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
            Document document = documentBuilder.parse(file);
            graphModel = parseDocument(document);
        }
        catch (ParserConfigurationException parserConfigurationException)
        {
            parserConfigurationException.printStackTrace();
        }
        catch (SAXException saxException)
        {
            saxException.printStackTrace();
        }
        catch (IOException ioException)
        {
            ioException.printStackTrace();
        }
        return graphModel;
    }

    public GraphModel parse(String fileName)
    {
        File file = new File(fileName);
        return parse(file);
    }

    private GraphModel parseDocument(Document document)
    {
        Element docElement = document.getDocumentElement();
        NodeList nodesNodeList = docElement.getElementsByTagName(NODE_TAG_NAME);
        Nodes nodes = parseNodes(nodesNodeList);
        NodeList edgesNodeList = docElement.getElementsByTagName(EDGE_TAG_NAME);
        Edges edges = parseEdges(edgesNodeList);
        Element settingsNode = (Element) docElement.getElementsByTagName(SETTINGS_TAG_NAME).item(0);
        Settings settings = parseSettings(settingsNode);

        return new GraphModel(nodes, edges, settings);
    }

    private Settings parseSettings(Element settingsNode)
    {
        double defaultNodeReliability = getDoubleValue(settingsNode, DEFAULT_NODE_RELIABILITY_TAG_NAME);
        double defaultEdgeReliability = getDoubleValue(settingsNode, DEFAULT_EDGE_RELIABILITY_TAG_NAME);
        int numberOfTests = getIntValue(settingsNode, NUMBER_OF_TESTS_TAG_NAME);
        return new Settings(defaultNodeReliability, defaultEdgeReliability, numberOfTests);
    }

    private String getTextValue(Element element, String tagName)
    {
        String textVal = null;
        NodeList nodeList = element.getElementsByTagName(tagName);
        if (nodeList != null && nodeList.getLength() > 0)
        {
            Element el = (Element) nodeList.item(0);
            textVal = el.getFirstChild().getNodeValue();
        }

        return textVal;
    }

    private int getIntValue(Element element, String tagName)
    {
        return Integer.parseInt(getTextValue(element, tagName));
    }

    private double getDoubleValue(Element element, String tagName)
    {
        String textValue = getTextValue(element, tagName);
        return Double.parseDouble(formatReliabilityString(textValue));
    }

    private Nodes parseNodes(NodeList nodesNodeList)
    {
        Nodes nodes = new Nodes();
        for (int i = 0; i < nodesNodeList.getLength(); i++)
        {
            Element nodeElement = (Element) nodesNodeList.item(i);
            String reliabilityStr = nodeElement.getAttribute(RELIABILITY_ATTRIBUTE_NAME);
            String indexStr = nodeElement.getAttribute(INDEX_ATTRIBUTE_NAME);
            String xStr = nodeElement.getAttribute(X_ATTRIBUTE_NAME);
            String yStr = nodeElement.getAttribute(Y_ATTRIBUTE_NAME);
            try
            {
                Integer key = Integer.parseInt(indexStr);
                Double reliability = Double.parseDouble(formatReliabilityString(reliabilityStr));
                int row = Integer.parseInt(yStr);
                int column = Integer.parseInt(xStr);
                nodes.put(key, new Node(reliability, row, column));
            }
            catch (NumberFormatException numberFormatException)
            {
                numberFormatException.printStackTrace();
            }
        }
        return nodes;
    }

    private Edges parseEdges(NodeList edgesNodeList)
    {
        Edges edges = new Edges();
        for (int i = 0; i < edgesNodeList.getLength(); i++)
        {
            Element edgeElement = (Element) edgesNodeList.item(i);
            String reliabilityStr = edgeElement.getAttribute(RELIABILITY_ATTRIBUTE_NAME);
            String indexStr = edgeElement.getAttribute(INDEX_ATTRIBUTE_NAME);
            String node1Str = edgeElement.getAttribute(NODE1_ATTRIBUTE_NAME);
            String node2Str = edgeElement.getAttribute(NODE2_ATTRIBUTE_NAME);
            try
            {
                Integer key = Integer.parseInt(indexStr);
                Double reliability = Double.parseDouble(formatReliabilityString(reliabilityStr));
                Integer node1Key = Integer.parseInt(node1Str);
                Integer node2Key = Integer.parseInt(node2Str);
                edges.put(key, new Edge(reliability, node1Key, node2Key));
            }
            catch (NumberFormatException numberFormatException)
            {
                numberFormatException.printStackTrace();
            }
        }
        return edges;
    }

    private String formatReliabilityString(String reliabilityStr)
    {
        return reliabilityStr.replace(',', '.');
    }

}
